/**
 *	@file    aesGCM.c
 *	@version 8e6f0e1 (HEAD -> master)
 *
 *	AES GCM block cipher implementation.
 */
/*
 *	Copyright (c) 2013-2016 INSIDE Secure Corporation
 *	Copyright (c) PeerSec Networks, 2002-2011
 *	All Rights Reserved
 *
 *	The latest version of this code is available at http://www.matrixssl.org
 *
 *	This software is open source; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This General Public License does NOT permit incorporating this software
 *	into proprietary programs.  If you are unable to comply with the GPL, a
 *	commercial license for this software may be purchased from INSIDE at
 *	http://www.insidesecure.com/
 *
 *	This program is distributed in WITHOUT ANY WARRANTY; without even the
 *	implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *	See the GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *	http://www.gnu.org/copyleft/gpl.html
 */
/******************************************************************************/

#include "../cryptoApi.h"

#ifdef USE_MATRIX_AES_GCM

/******************************************************************************/

static void psGhashPad(psAesGcm_t *ctx);
static void psGhashInit(psAesGcm_t *ctx,
				const unsigned char *GHASHKey_p);
static void psGhashUpdate(psAesGcm_t *ctx, const unsigned char *data,
				uint32 dataLen, int dataType);
static void psGhashFinal(psAesGcm_t *ctx);

#define GHASH_DATATYPE_AAD			0
#define GHASH_DATATYPE_CIPHERTEXT	2

#define FL_GET_BE32(be)                           \
	(((uint32)((be).be_bytes[0]) << 24) |         \
	 ((uint32)((be).be_bytes[1]) << 16) |         \
	 ((uint32)((be).be_bytes[2]) <<  8) |         \
	 (uint32)((be).be_bytes[3]))

typedef struct { unsigned char be_bytes[4]; } FL_UInt32_BE_UNA_t;

#define FLFBLOCKSIZE 128 /* Maximum block size of a hash function. */

/******************************************************************************/
/*
	Initialize an AES GCM context
*/
int32_t psAesInitGCM(psAesGcm_t *ctx,
					const unsigned char key[AES_MAXKEYLEN], uint8_t keylen)
{
	int32_t			rc;
	unsigned char	blockIn[16] = { 0 };

	memset(ctx, 0x0, sizeof(psAesGcm_t));
	/* GCM always uses AES in ENCRYPT block mode, even for decrypt */
	rc = psAesInitBlockKey(&ctx->key, key, keylen, PS_AES_ENCRYPT);
	if (rc < 0) {
		return rc;
	}
	psAesEncryptBlock(&ctx->key, blockIn, ctx->gInit);
	return PS_SUCCESS;
}

/******************************************************************************/

void psAesClearGCM(psAesGcm_t *ctx)
{
    /* Only need to clear block if it's implemented externally, Matrix block
        is part of AesGcm_t and will be cleared below */
#ifndef USE_MATRIX_AES_BLOCK
    psAesClearBlockKey(&ctx->key);
#endif
    memset_s(ctx, sizeof(psAesGcm_t), 0x0, sizeof(psAesGcm_t));
}

/******************************************************************************/
/*
	Specifiy the IV and additional data to an AES GCM context that was
	created with psAesInitGCM
*/
void psAesReadyGCM(psAesGcm_t *ctx,
					const unsigned char IV[AES_IVLEN],
					const unsigned char *aad, uint16_t aadLen)
{
	psGhashInit(ctx, ctx->gInit);
	/* Save aside first counter for final use */
	memset(ctx->IV, 0, 16);
	memcpy(ctx->IV, IV, 12);
	ctx->IV[15] = 1;

	/* Set up crypto counter starting at nonce || 2 */
	memset(ctx->EncCtr, 0, 16);
	memcpy(ctx->EncCtr, IV, 12);
	ctx->EncCtr[15] = 2;

	psGhashUpdate(ctx, aad, aadLen, GHASH_DATATYPE_AAD);
	psGhashPad(ctx);
}

/******************************************************************************/
/*
	Internal gcm crypt function that uses direction to determine what gets
	fed to the GHASH update
*/
static void psAesEncryptGCMx(psAesGcm_t *ctx,
				const unsigned char *pt, unsigned char *ct,
				uint32_t len, int8_t direction)
{
	unsigned char	*ctStart;
	uint32_t		outLen;
	int				x;	/* Must be signed due to positive check below */

	outLen = len;
	ctStart = ct;
	if (direction == 0) {
		psGhashUpdate(ctx, pt, len, GHASH_DATATYPE_CIPHERTEXT);
	}
	while (len) {
		if (ctx->OutputBufferCount == 0) {
			ctx->OutputBufferCount = 16;
			psAesEncryptBlock(&ctx->key, ctx->EncCtr, ctx->CtrBlock);

			/* CTR incr */
			for (x = (AES_BLOCKLEN - 1); x >= 0; x--) {
				ctx->EncCtr[x] = (ctx->EncCtr[x] +
					(unsigned char)1) & (unsigned char)255;
				if (ctx->EncCtr[x] != (unsigned char)0) {
					break;
				}
			}
		}

		*(ct++) = *(pt++) ^ ctx->CtrBlock[16 - ctx->OutputBufferCount];
		len--;
		ctx->OutputBufferCount--;
	}
	if (direction == 1) {
		psGhashUpdate(ctx, ctStart, outLen, GHASH_DATATYPE_CIPHERTEXT);
	}
}

/******************************************************************************/
/*
	Public GCM encrypt function.  This will just perform the encryption.  The
	tag should be fetched with psAesGetGCMTag
*/
void psAesEncryptGCM(psAesGcm_t *ctx,
					const unsigned char *pt, unsigned char *ct,
					uint32_t len)
{
	psAesEncryptGCMx(ctx, pt, ct, len, 1);
}

/******************************************************************************/
/*
	After encryption this function is used to retreive the authentication tag
*/
void psAesGetGCMTag(psAesGcm_t *ctx,
				uint8_t tagBytes, unsigned char tag[AES_BLOCKLEN])
{
	unsigned char	*pt, *ct;

	psGhashFinal(ctx);

	/* Encrypt authentication tag */
	ctx->OutputBufferCount = 0;

	ct = tag;
	pt = (unsigned char*)ctx->TagTemp;
	while (tagBytes) {
		if (ctx->OutputBufferCount == 0) {
			ctx->OutputBufferCount = 16;
			/* Initial IV has been set aside in IV */
			psAesEncryptBlock(&ctx->key, ctx->IV, ctx->CtrBlock);
			/* No need to increment since we know tag bytes will never be
				larger than 16 */
		}
		*(ct++) = *(pt++) ^ ctx->CtrBlock[16 - ctx->OutputBufferCount];
		tagBytes--;
		ctx->OutputBufferCount--;

	}
}

/* Just does the GCM decrypt portion.  Doesn't expect the tag to be at the end
	of the ct.  User will invoke psAesGetGCMTag seperately */
void psAesDecryptGCMtagless(psAesGcm_t *ctx,
				const unsigned char *ct, unsigned char *pt,
				uint32_t len)
{
	psAesEncryptGCMx(ctx, ct, pt, len, 0);
}


/******************************************************************************/
/*
	Decrypt will invoke GetGMCTag so the comparison can be done.  ctLen
	will include the appended tag length and ptLen is just the encrypted
	portion
*/
int32_t psAesDecryptGCM(psAesGcm_t *ctx,
				const unsigned char *ct, uint32_t ctLen,
				unsigned char *pt, uint32_t ptLen)
{
	uint16_t		tagLen;
	unsigned char	tag[AES_BLOCKLEN];

	if (ctLen > ptLen) {
		tagLen = ctLen - ptLen;
	} else {
		return PS_ARG_FAIL;
	}

	psAesEncryptGCMx(ctx, ct, pt, ptLen, 0);
	psAesGetGCMTag(ctx, tagLen, tag);

	if (memcmpct(tag, ct + ptLen, tagLen) != 0) {
		psTraceCrypto("GCM didn't authenticate\n");
		return PS_AUTH_FAIL;
	}
	return PS_SUCCESS;
}

/******************************************************************************/
/*
	Ghash code taken from FL
*/
static void FLA_GHASH_128_mul_base(uint32 *op, uint32 moduli)
{
  int carry_bit = op[3] & 0x1;

  op[3] = op[3] >> 1 | (op[2] & 0x1) << 31;
  op[2] = op[2] >> 1 | (op[1] & 0x1) << 31;
  op[1] = op[1] >> 1 | (op[0] & 0x1) << 31;
  op[0] = op[0] >> 1;

  if (carry_bit)
	  op[0] ^= moduli;
}

/* Multiplication of X by Y, storing the result to X. */
static void FLA_GHASH_128_mul(uint32 *X, const uint32 *Y, uint32 moduli)
{
  uint32 t[4];
  int i;

  t[0] = X[0];
  t[1] = X[1];
  t[2] = X[2];
  t[3] = X[3];

  X[0]= X[1] = X[2] = X[3] = 0;

 for (i = 0; i < 128; i++)
	{
	  if (Y[i / 32] & (1 << (31 - i % 32)))
		{
		  X[0] ^= t[0];
		  X[1] ^= t[1];
		  X[2] ^= t[2];
		  X[3] ^= t[3];
		}

	  FLA_GHASH_128_mul_base(t, moduli);
	}
}

static int FLFIncreaseCountBits(psAesGcm_t * ctx, unsigned int CounterId,
				int32 NBits)
{
	int32 Lo, Hi;
	int32 Temp;

	Lo = NBits;
	Hi = 0;

	Temp = ctx->ProcessedBitCount[CounterId];
	ctx->ProcessedBitCount[CounterId] += Lo;

	if (Temp > (int32)ctx->ProcessedBitCount[CounterId])
	{
		Hi += 1;
	}

	if (Hi)
	{
		Temp = ctx->ProcessedBitCount[CounterId + 1];
		ctx->ProcessedBitCount[CounterId + 1] += Hi;

		/* Returns true if carry out of highest bits. */
		return (Temp > (int32)ctx->ProcessedBitCount[CounterId + 1]);
	}

	/* No update of high order bits => No carry. */
	return 0; /* false */
}

static int increaseCountBytes(psAesGcm_t *ctx, int32 NBytes,
				int CounterId)
{
	int carry;

	/* COVN: Test this code with > 2^31 bits. */

	/* Process NBytes (assuming NBytes < 0x10000000) */
	carry = FLFIncreaseCountBits(ctx, CounterId, (NBytes & 0x0FFFFFFF) << 3);

	NBytes &= 0x0FFFFFFF;

	/* For unusually large values of NBytes, process the remaining bytes
	   to add 0x10000000 at time. This ensure the value of bytes,
	   once converted to bits, does not overflow 32-bit value.

	   PORTN: It is assumed NBytes <= 2**61. This is true on 32-bit APIs as
	   FL_DataLen_t cannot represent such large value. */
	while(NBytes >= 0x10000000)
	{
		carry |= FLFIncreaseCountBits(ctx, CounterId, 0x10000000U * 8);
		NBytes -= 0x10000000;
	}

	return carry;
}

static void FLAGcmProcessBlock(uint32 *H, FL_UInt32_BE_UNA_t *Buf_p,
				uint32 *InOut)
{
	/* PORTN: Requires sizeof(FL_UInt32_BE_UNA_t) to be 4.
	   Some platforms may add padding to FL_UInt32_BE_UNA_t if
	   it is represented as a structure. */

	InOut[0] ^= FL_GET_BE32(Buf_p[0]);
	InOut[1] ^= FL_GET_BE32(Buf_p[1]);
	InOut[2] ^= FL_GET_BE32(Buf_p[2]);
	InOut[3] ^= FL_GET_BE32(Buf_p[3]);

  FLA_GHASH_128_mul(InOut, H, (1 << 31) + (1 << 30) + (1 << 29) + (1 << 24));
}

static void UpdateFunc(psAesGcm_t *ctx, const unsigned char *Buf_p,
				  int32 Size)
{
	while (Size >= 16)
	{
		FLAGcmProcessBlock((uint32*)ctx->Hash_SubKey,
			(FL_UInt32_BE_UNA_t *) Buf_p, (uint32*)ctx->TagTemp);
		Buf_p += 16;
		Size -= 16;
	}
}

static void flf_blocker(psAesGcm_t *ctx, const unsigned char *Data_p,
						uint32 DataCount)
{
	while (DataCount > 0)
	{
		if (ctx->InputBufferCount == FLFBLOCKSIZE)
		{
			UpdateFunc(ctx,
					   ctx->Input.Buffer,
					   ctx->InputBufferCount);
			ctx->InputBufferCount = 0;
		}
		if (ctx->InputBufferCount < FLFBLOCKSIZE)
		{
			uint32 BytesProcess = min((uint32)DataCount,
				FLFBLOCKSIZE - ctx->InputBufferCount);

			memcpy(ctx->Input.Buffer
				+ ctx->InputBufferCount, Data_p, BytesProcess);
			DataCount -= BytesProcess;
			ctx->InputBufferCount += BytesProcess;
			Data_p += BytesProcess;
		}
	}

}

static void psGhashInit(psAesGcm_t *ctx,
						const unsigned char *GHASHKey_p)
{
	uint32 *Key_p = (uint32*)ctx->Hash_SubKey;

	memset(&ctx->ProcessedBitCount, 0x0,
		sizeof(ctx->ProcessedBitCount));
	ctx->InputBufferCount = 0;
	Key_p[0] = FL_GET_BE32(*(FL_UInt32_BE_UNA_t *) GHASHKey_p);
	Key_p[1] = FL_GET_BE32(*(FL_UInt32_BE_UNA_t *) (GHASHKey_p + 4));
	Key_p[2] = FL_GET_BE32(*(FL_UInt32_BE_UNA_t *) (GHASHKey_p + 8));
	Key_p[3] = FL_GET_BE32(*(FL_UInt32_BE_UNA_t *) (GHASHKey_p + 12));
	memset(ctx->TagTemp, 0x0, 16);
}

static void psGhashUpdate(psAesGcm_t *ctx, const unsigned char *data,
							uint32 dataLen, int dataType)
{
	increaseCountBytes(ctx, dataLen, dataType);
	flf_blocker(ctx, data, dataLen);

}

static void psGhashPad(psAesGcm_t *ctx)
{
	while ((ctx->InputBufferCount & 15) != 0)
	{
		unsigned char z = 0;
		flf_blocker(ctx, &z, 1);
	}
}

#ifndef ENDIAN_BIG
#ifdef ENDIAN_NEUTRAL
#warning ENDIAN_NEUTRAL untested for AES-GCM
#endif

/* On little endian (and endian neutral untested), we need to reverse bytes
	to big endian mode for GhashFinal */
static uint32 FLM_ReverseBytes32(uint32 Value)
{
	Value = (((Value & 0xff00ff00UL) >> 8) | ((Value & 0x00ff00ffUL) << 8));
	return ((Value >> 16) | (Value << 16));
}
static void FLF_ConvertToBE64(uint32 *Swap_p, uint32 num)
{
	uint32 tmp;
	while(num) {
		num--;
		tmp = FLM_ReverseBytes32(Swap_p[num * 2]);
		Swap_p[num * 2] = FLM_ReverseBytes32(Swap_p[num * 2 + 1]);
		Swap_p[num * 2 + 1] = tmp;
	}
}
static void FLF_ConvertToBE32(uint32 *Swap_p, uint32 num)
{
	/* PORTN: Byte order specific function. */
	while(num)
	{
		num--;
		Swap_p[num] = FLM_ReverseBytes32(Swap_p[num]);
	}
}

#else

/* On big endian, the 4 bytes in a uint32 are already in the right order! */
static void FLF_ConvertToBE64(uint32 *Swap_p, uint32 num)
{
	uint32 tmp;
	while(num) {
		num--;
		tmp = Swap_p[num * 2];
		Swap_p[num * 2] = Swap_p[num * 2 + 1];
		Swap_p[num * 2 + 1] = tmp;
	}
}
#define FLF_ConvertToBE32(A, B)

#endif /* !ENDIAN_BIG */

static void psGhashFinal(psAesGcm_t *ctx)
{
	psGhashPad(ctx);

	/* TODO COVN: Check GHASH with data > 2^32 bits for word swap logic. */
	/* Swap 32 bit words 0 & 1 and 2 & 3, on ENDIAN_LITTLE, swap bytes too */
	FLF_ConvertToBE64(&ctx->ProcessedBitCount[0], 2);

	UpdateFunc(ctx, ctx->Input.Buffer,
		ctx->InputBufferCount);
	ctx->InputBufferCount = 0;

	UpdateFunc(ctx,
		(const unsigned char *) &ctx->ProcessedBitCount[0], 16);

	/* Convert temporary Tag Value to final Tag Value. NOOP on ENDIAN_BIG */
	FLF_ConvertToBE32(ctx->TagTemp, 4);
}

#endif /* USE_MATRIX_AES_GCM */

/******************************************************************************/

